#!/bin/bash
#
# **lib/meta-config** - Configuration file manipulation functions
#
# Support for DevStack's local.conf meta-config sections
#
# These functions have no external dependencies and the following side-effects:
#
# CONFIG_AWK_CMD is defined, default is ``awk``

# Meta-config files contain multiple INI-style configuration files
# using a specific new section header to delimit them:
#
#   [[group-name|file-name]]
#
# group-name refers to the group of configuration file changes to be processed
# at a particular time.  These are called phases in ``stack.sh`` but
# group here as these functions are not DevStack-specific.
#
# file-name is the destination of the config file

# Save trace setting
_XTRACE_INC_META=$(set +o | grep xtrace)
set +o xtrace


# Allow the awk command to be overridden on legacy platforms
CONFIG_AWK_CMD=${CONFIG_AWK_CMD:-awk}

# Get the section for the specific group and config file
# get_meta_section infile group configfile
function get_meta_section {
    local file=$1
    local matchgroup=$2
    local configfile=$3

    [[ -r $file ]] || return 0
    [[ -z $configfile ]] && return 0

    $CONFIG_AWK_CMD -v matchgroup=$matchgroup -v configfile=$configfile '
        BEGIN { group = "" }
        /^\[\[.+\|.*\]\]/ {
            gsub("[][]", "", $1);
            split($1, a, "|");
            if (a[1] == matchgroup && a[2] == configfile) {
                group=a[1]
            } else {
                group=""
            }
            next
        }
        {
            if (group != "")
                print $0
        }
    ' $file
}


# Get a list of config files for a specific group
# get_meta_section_files infile group
function get_meta_section_files {
    local file=$1
    local matchgroup=$2

    [[ -r $file ]] || return 0

    $CONFIG_AWK_CMD -v matchgroup=$matchgroup '
        /^\[\[.+\|.*\]\]/ {
            gsub("[][]", "", $1);
            split($1, a, "|");
            if (a[1] == matchgroup)
                print a[2]
        }
    ' $file
}


# Merge the contents of a meta-config file into its destination config file
# If configfile does not exist it will be created.
# merge_config_file infile group configfile
function merge_config_file {
    local file=$1
    local matchgroup=$2
    local configfile=$3

    # note, configfile might be a variable (note the iniset, etc
    # created in the mega-awk below is "eval"ed too, so we just leave
    # it alone.
    local real_configfile
    real_configfile=$(eval echo $configfile)
    if [ ! -f $real_configfile ]; then
        touch $real_configfile || die $LINENO "could not create config file $real_configfile ($configfile)"
    fi

    get_meta_section $file $matchgroup $configfile | \
    $CONFIG_AWK_CMD -v configfile=$configfile '
        BEGIN {
            section = ""
            last_section = ""
            section_count = 0
        }
        /^\[.+\]/ {
            gsub("[][]", "", $1);
            section=$1
            next
        }
        /^ *\#/ {
            next
        }
        /^[^ \t]+/ {
            # get offset of first '=' in $0
            eq_idx = index($0, "=")
            # extract attr & value from $0
            attr = substr($0, 1, eq_idx - 1)
            value = substr($0, eq_idx + 1)
            # only need to strip trailing whitespace from attr
            sub(/[ \t]*$/, "", attr)
            # need to strip leading & trailing whitespace from value
            sub(/^[ \t]*/, "", value)
            sub(/[ \t]*$/, "", value)

            # cfg_attr_count: number of config lines per [section, attr]
            # cfg_attr: three dimensional array to keep all the config lines per [section, attr]
            # cfg_section: keep the section names in the same order as they appear in local.conf
            # cfg_sec_attr_name: keep the attr names in the same order as they appear in local.conf
            if (! (section, attr) in cfg_attr_count) {
                if (section != last_section) {
                    cfg_section[section_count++] = section
                    last_section = section
                }
                attr_count = cfg_sec_attr_count[section_count - 1]++
                cfg_sec_attr_name[section_count - 1, attr_count] = attr

                cfg_attr[section, attr, 0] = value
                cfg_attr_count[section, attr] = 1
            } else {
                lno = cfg_attr_count[section, attr]++
                cfg_attr[section, attr, lno] = value
            }
        }
        END {
            # Process each section in order
            for (sno = 0; sno < section_count; sno++) {
                section = cfg_section[sno]
                # The ini routines simply append a config item immediately
                # after the section header. To keep the same order as defined
                # in local.conf, invoke the ini routines in the reverse order
                for (attr_no = cfg_sec_attr_count[sno] - 1; attr_no >=0; attr_no--) {
                    attr = cfg_sec_attr_name[sno, attr_no]
                    if (cfg_attr_count[section, attr] == 1)
                        print "iniset " configfile " " section " " attr " \"" cfg_attr[section, attr, 0] "\""
                    else {
                        # For multiline, invoke the ini routines in the reverse order
                        count = cfg_attr_count[section, attr]
                        print "inidelete " configfile " " section " " attr
                        print "iniset " configfile " " section " " attr " \"" cfg_attr[section, attr, count - 1] "\""
                        for (l = count -2; l >= 0; l--)
                            print "iniadd_literal " configfile " " section " " attr " \"" cfg_attr[section, attr, l] "\""
                    }
                }
            }
        }
    ' | while read a; do eval "$a"; done
}


# Merge all of the files specified by group
# merge_config_group infile group [group ...]
function merge_config_group {
    local localfile=$1; shift
    local matchgroups=$@

    [[ -r $localfile ]] || return 0

    local configfile group
    for group in $matchgroups; do
        for configfile in $(get_meta_section_files $localfile $group); do
            local realconfigfile
            local dir

            realconfigfile=$(eval "echo $configfile")
            if [[ -z $realconfigfile ]]; then
                warn $LINENO "unknown config file specification: $configfile is undefined"
                break
            fi
            dir=$(dirname $realconfigfile)

            test -e $dir && ! test -d $dir && die $LINENO "bogus config file specification $configfile ($configfile=$realconfigfile, $dir exists but it is not a directory)"

            if ! [[ -e $dir ]] ; then
                sudo mkdir -p $dir || die $LINENO "could not create the directory of $real_configfile ($configfile)"
                sudo chown ${STACK_USER} $dir
            fi

            merge_config_file $localfile $group $configfile
        done
    done
}

function extract_localrc_section {
    local configfile=$1    # top_dir/local.conf
    local localrcfile=$2   # top_dir/localrc
    local localautofile=$3 # top_dir/.localrc.auto

    if [[ -r $configfile ]]; then
        LRC=$(get_meta_section_files $configfile local)
        for lfile in $LRC; do
            if [[ "$lfile" == "localrc" ]]; then
                if [[ -r $localrcfile ]]; then
                    echo "localrc and local.conf:[[local]] both exist, using localrc"
                else
                    echo "# Generated file, do not edit" >$localautofile
                    get_meta_section $configfile local $lfile >>$localautofile
                fi
            fi
        done
    fi
}

# Restore xtrace
$_XTRACE_INC_META

# Local variables:
# mode: shell-script
# End:
